package com.eshop.aspect;

import org.slf4j.LoggerFactory;
import org.aspectj.lang.annotation.Around;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import com.eshop.exception.BadLimitRequestException;
import java.util.List;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import com.google.common.collect.ImmutableList;
import com.eshop.utils.StringUtils;
import com.eshop.annotation.Limit;
import org.aspectj.lang.reflect.MethodSignature;
import com.eshop.utils.RequestHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Aspect;

@Aspect
@Component
public class LimitAspect
{
    private final RedisTemplate<Object, Object> redisTemplate;
    private static final Logger logger;
    
    public LimitAspect(final RedisTemplate<Object, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    @Pointcut("@annotation(com.eshop.annotation.Limit)")
    public void pointcut() {
    }
    
    @Around("pointcut()")
    public Object around(final ProceedingJoinPoint joinPoint) throws Throwable {
        final HttpServletRequest request = RequestHolder.getHttpServletRequest();
        final MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        final Method signatureMethod = signature.getMethod();
        final Limit limit = signatureMethod.getAnnotation(Limit.class);
        final LimitType limitType = limit.limitType();
        //获取key
        String key = limit.key();
        //如果key为空
        if (StringUtils.isEmpty((CharSequence)key)) {
            //看limitType是不是设置的IP，是的话就设置key为IP
            if (limitType == LimitType.IP) {
                key = StringUtils.getIp(request);
            }
            //否则key为方法名
            else {
                key = signatureMethod.getName();
            }
        }
        final ImmutableList<Object> keys = (ImmutableList<Object>)ImmutableList.of((Object)StringUtils.join((Object[])new String[] { limit.prefix(), "_", key, "_", request.getRequestURI().replaceAll("/", "_") }));
        //生成redis脚本
        final String luaScript = this.buildLuaScript();
        final RedisScript<Number> redisScript = (RedisScript<Number>)new DefaultRedisScript(luaScript, (Class)Number.class);
        final Number count = (Number)this.redisTemplate.execute((RedisScript)redisScript, (List)keys, new Object[] { limit.count(), limit.period() });
        //判断是否超过访问次数
        if (null != count && count.intValue() <= limit.count()) {
            LimitAspect.logger.info("第{}次访问key为 {}，描述为 [{}] 的接口", new Object[] { count, keys, limit.name() });
            return joinPoint.proceed();
        }
        throw new BadLimitRequestException("访问次数受限制");
    }
    
    private String buildLuaScript() {
        return "local c\nc = redis.call('get',KEYS[1])\nif c and tonumber(c) > tonumber(ARGV[1]) then\nreturn c;\nend\nc = redis.call('incr',KEYS[1])\nif tonumber(c) == 1 then\nredis.call('expire',KEYS[1],ARGV[2])\nend\nreturn c;";
    }
    
    static {
        logger = LoggerFactory.getLogger((Class)LimitAspect.class);
    }
}
